var rKey = /^[$_a-zA-A][$._a-zA-Z\d]*$|^[ ]{0,1}$/,
    // isTextBind = new RegExp("jc-bind[ ]+"),
    filterErr = minErr("filter"),
    scanErr = minErr('scan');

var removableAttr = oneObject("checked,selected");
var jcInnerEvent = 'jcInnerEvent';

/**
 * 根据传递进来的数据,判定当前路径是否是在`jc-repeat`标签下的，
 * 然后做相应的处理，并返回真正的索引路径
 *
 * @namespace parseAnalyze
 * @param  {Object} data
 * @param  {string} path
 * @return {string}
 */
function repeatPathCheck(data, path) {
    if (data.$isForEach) {
        var p = path.split(".")
        if (p.length > 1) {
            p = p[0]
            path = data[p].$path + "." + data[p].$index + path.substring(path.indexOf("."), path.length)
        } else {
            path = data.$path + "." + data.$index
        }

    }
    return path
}
/**
 * 根据 `bind.type`划分到不同的模块中，
 * 进行相应的处理
 *
 * TODO:更改为管道过滤
 * @namespace parseAnalyze
 * @param  {array} binds
 * @param  {object} data
 */
function scanDispatch(binds, data, th) {
    var type, bind

    while (bind = binds.shift()) {

        type = bind.type
        if (bindMod[type]) {
            bindMod[type](th, bind, data, binds)
        } else {
            if (!scanMod[type]) {
                if (type === 'id')
                    filterErr('Do not support jc-id! because this emits great bad smell of coding.')
                bind.param = bind.type
                bind.type = type = 'cal'
            }
            scanMod[type](th, bind, data, binds)
        }
    }
}

function exprRunner(data, value, filters) {
    if (arguments.length === 2) {
        var valExpr = scanExpr(openTag + value + closeTag, data)[0];
        value = valExpr.value
        filters = valExpr.filters
    }
    if (rKey.test(value)) {
        value = dotPath(data, value)
    } else {
        value = Function.apply(noop, ["return " + value]).call(data)
    }
    return filterRunner(value, filters)
}

function filterRunner(value, filters) {
    if (filters) {
        for (var i = 0, f; f = filters[i++];) {
            if (typeof filtersMod[f.method] === "function") {
                value = filtersMod[f.method].apply(null, [value].concat(f.args))
            } else {
                filterErr("jc.filters does not hava {1} method or jc.filters.{1} is not a function", f.method)
            }
        }
    }
    return value
}

var bindMod = jc.bindMod = {
    reuse: function(th, bind, data, binds) {
        var elem = bind.element;

        var fragInfo = {
                tpl: bind.element.innerHTML,
                elem: elem
            },
            id = bind.value;

        var frags = th._data("frags")
        if (frags) {
            frags[id] = fragInfo
        } else {
            var reuseFrag = {}
            reuseFrag[id] = fragInfo
            th._data("frags", reuseFrag)
        }
        if (bind.param === 'hide') {
            elem.style.display = 'none'
            binds.length = 0
        }
        bind.element.removeAttribute(bind.name)
    },
    bind: function(th, bind, data) {
        var elem = bind.element,
            bindType = bind.param,
            valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
            path = repeatPathCheck(data, valExpr.value).split("."),
            filters = valExpr.filters,
            defaultTriggerEvent = 'input';

        if (elem.nodeName === 'SELECT') {
            defaultTriggerEvent = 'change'
        } else if (elem.type === 'radio') {
            defaultTriggerEvent = 'click'
        }
        //看看能不能重构简化
        if (elem.type === 'checkbox') { //checkbox 特殊处理
            elem.addEventListener(bindType || 'click', function(event) {
                arrPathVal(th._data("data"), path, filterRunner(Boolean(event.target.checked), filters))
            })
            elem.addEventListener(jcInnerEvent, function() {
                arrPathVal(th._data("data"), path, filterRunner(Boolean(event.target.checked), filters))
            })
        } else if (elem.type === 'number') {
            elem.addEventListener(bindType || defaultTriggerEvent, function(event) {
                arrPathVal(th._data("data"), path, filterRunner(Number(event.target.value), filters))
            })
            elem.addEventListener(jcInnerEvent, function() {
                arrPathVal(th._data("data"), path, filterRunner(Number(event.target.value), filters))
            })
        } else {
            elem.addEventListener(bindType || defaultTriggerEvent, function(event) {
                arrPathVal(th._data("data"), path, filterRunner(event.target.value, filters))
            })
            elem.addEventListener(jcInnerEvent, function() {
                arrPathVal(th._data("data"), path, filterRunner(event.target.value, filters))
            })
        }
        elem.removeAttribute(bind.name)
    },
    duplex: function(th, bind, data) {
        this.bind(th, bind, data)
        watchDispatcher(th, bind, data, exprRunner(data, bind.value))
    },
    dupfilter: function(th, bind, data) {
        var elem = bind.element
        elem.removeAttribute(bind.name)
        dataMod.data(elem, "dupfilter", bind.value)
    },
    reset: function(th, bind, data) {
        var elem = bind.element,
            bindType = bind.param;
        elem.addEventListener(bindType || 'click', function() {
            var form = elem.form,
                e = document.createEvent('HTMLEvents');
            e.initEvent(jcInnerEvent, false, true)
            form.reset()
            for (var i = 0, f; f = form[i++];) {
                f.dispatchEvent(e)
            }
        })
        //预防用错
        // if (elem.nodeName === 'INPUT') {
        //     elem.type = 'button'
        // }
    },
    attr: function(th, bind, data) {
        if (bind.param === 'class')
            scanErr(" oops! now we not support the class tag bind")
        var elem = bind.element

        elem.removeAttribute(bind.name)
        elem.setAttribute(bind.param, exprRunner(data, bind.value))
        watchDispatcher(th, bind, data)
    },
    repeat: function(th, bind, data, binds) {
        // type: type,
        // param: param,
        // element: elem,
        // name: match[0],
        // value: attr.value,

        var eleData, value,
            index = bind.param,
            tpl = bind.element,
            parentNode = tpl.parentNode;

        binds.length = 0 //不再执行剩下 绑定,留给元素自己渲染执行

        if (data.$isForEach) {
            eleData = data
        } else {
            eleData = {
                $isForEach: true,
                $data: data
            }
        }
        value = dotPath(data, bind.value)
        //use regex to improve performance
        var $path = bind.value.split(".")
        var pathHead = $path.shift()
        if (pathHead === '$data') {
            $path = $path.join('.')
        } else {
            var parentPath = (eleData[pathHead] || UniObj).$path
            if (parentPath !== undefined) {
                parentPath = parentPath + "." + eleData[pathHead].$index
                $path.unshift(parentPath)
                $path = $path.join(".")
            } else {
                $path = bind.value
            }
        }

        if (value === undefined) {
            scanErr("the `jc-repeat` tag value in tempate {1} should not undefined! the path is {2}", th._data("url"), $path)
        }

        var frag = document.createDocumentFragment();
        tpl.removeAttribute(bind.name);
        var startComment = document.createComment("jc-repeat start")
        frag.appendChild(startComment)
        if (value.length === 0) {
            eleData.$path = $path
        } else {
            for (var i = 0; i < value.length; i++) {
                eleData[index] = value[i]
                if (typeof value[0] === 'object') {
                    eleData[index].$index = i
                    eleData[index].$path = $path
                } else {
                    eleData.$index = i
                    eleData.$path = $path
                }

                var newElem = tpl.cloneNode(true)
                frag.appendChild(newElem)
                scanAttr(frag.lastChild, eleData, th)
            }
        }
        var endComment = document.createComment("jc-repeat end") //用来做length＝0时的批量优化
        frag.appendChild(endComment)
        parentNode.replaceChild(frag, tpl)

        bind.startComment = startComment
        bind.endComment = endComment

        watchDispatcher(th, bind, eleData)
    },
    foreach: function(th, bind, data) {
        var parentNode = bind.element,
            tpl = parentNode.children,
            index = bind.param,
            tplArr = [],
            eleData,
            value, i;

        for (i = 0; i < tpl.length; i++) {
            tplArr.push(tpl[i])
        }
        parentNode.innerHTML = ""
        if (data.$isForEach) {
            eleData = data
        } else {
            eleData = {
                $isForEach: true,
                $data: data
            }
        }
        value = dotPath(data, bind.value)
        if (value === undefined) {
            scanErr("the `jc-foreach` tag value in tempate {1} should not be undefined! the path is {2}", th._data("url"), $path)
        }

        var $path = bind.value.split(".")
        var pathHead = $path.shift()
        if (pathHead === '$data') {
            $path = $path.join('.')
        } else {
            var parentPath = (eleData[pathHead] || UniObj).$path
            if (parentPath !== undefined) {
                parentPath = parentPath + "." + eleData[pathHead].$index
                $path.unshift(parentPath)
                $path = $path.join(".")
            } else {
                $path = bind.value
            }
        }
        var frag = document.createDocumentFragment()
        for (i = 0; i < value.length; i++) {
            eleData[index] = value[i]
            if (typeof value[0] === 'object') {
                eleData[index].$index = i
                eleData[index].$path = $path
            } else {
                eleData.$index = i
                eleData.$path = $path
            }
            for (var j = 0; j < tplArr.length; j++) {
                frag.appendChild(tplArr[j].cloneNode(true))
                scanAttr(frag.lastChild, eleData, th)
            }
        }
        parentNode.appendChild(frag)
        bind.tpl = tplArr
        watchDispatcher(th, bind, eleData)

    },
    if : function(th, bind, data, binds) {
        //  type: type,
        // param: param,
        // element: elem,
        // name: attr.name,
        // value: attr.value
        var value = !!exprRunner(data, bind.value),
            originValue = value,
            elem = bind.element;

        /*if (getType(value) !== 'boolean') {
            filterErr("the value {1} must be boolean type", value)
        }*/
        binds.length = 0
        if (bind.param === 'not')
            value = !value
        elem.removeAttribute(bind.name)
        var replaceChild;
        if (!value) {
            replaceChild = document.createComment("if-tag")
            elem.parentNode.replaceChild(replaceChild, elem)
        } else {
            replaceChild = elem.cloneNode(true)
            elem.parentNode.replaceChild(replaceChild, elem)
            scanAttr(replaceChild, data, th)
        }
        bind.realElem = replaceChild
        if (rKey.test(bind.value))
            watchDispatcher(th, bind, data, originValue)
    }
}

var scanMod = jc.scanMod = {
    text: function(th, bind, data) {
        var path = bind.value
        var v = exprRunner(data, path, bind.filters);
        bind.node.nodeValue = v
        if (bind.needWatch)
            watchDispatcher(th, bind, data, v)
    },
    cal: function(th, bind, data) {
        var elem = bind.element,
            attrKey = bind.name,
            rendAttrKey = bind.param;
        elem.removeAttribute(attrKey)
        var value = exprRunner(data, bind.value);


        if (rendAttrKey in removableAttr) {
            if (value)
                elem.setAttribute(rendAttrKey, '')
            else
                elem.removeAttribute(rendAttrKey)
        } else {
            elem.setAttribute(rendAttrKey, value)
        }
    },
    /**
     * class比较灵活
     * jc-class=表达式 会将表达式的结果 塞入本元素的className中
     * jc-class-name=表达式,如果表达式的结果在if中判定为true,
     * 则会将name塞入本元素的className中，否则，就将该name从
     * 本元素剔除出去
     *
     */
    "class": function(th, bind, data) {
        var elem = bind.element,
            addClass = bind.param,
            value = exprRunner(data, bind.value),
            originClass = elem.className;
        elem.removeAttribute(bind.name)
        if (addClass === '') {
            if (value !== '' && !originClass.match(new RegExp("(^|\\s)" + value + "($|\\s)"))) {
                elem.className = originClass + " " + value
            }
        } else {
            var classMatchReg = new RegExp("(^|\\s)" + addClass + "($|\\s)");
            if (value) {
                if (!originClass.match(classMatchReg)) {
                    elem.className = originClass + " " + addClass
                }
            } else {
                elem.className = originClass.replace(classMatchReg, "")
            }
        }


    },
    value: function(th, bind, data) {
        var elem = bind.element,
            attrKey = bind.name;
        var value = exprRunner(data, bind.value);
        elem.removeAttribute(attrKey)

        if (elem.nodeName === 'TEXTAREA') {
            elem.value = value
        } else
            elem.setAttribute('value', value)
    }

}

var filtersMod = jc.filters = {

    /**
     * @example
     * {{value | defaultVal dv}}
     * 默认值配置 如果value 为 null , undefined 则会替换成默认值
     *
     * @param  {string} value         内部系统参数
     * @param  {string=} dv           默认值为""
     * @return {string}
     */
    defaultVal: function(value, dv) {
        if (value === undefined || value === null)
            return dv || "";
        return value
    },

    toBoolean: function(value, equalVal) {
        if (equalVal) {
            if (value === equalVal)
                return true
            return false
        }
        if (value)
            return true
        return false
    },
    sampleMap: function(value) {
        var length = arguments.length
        if (length % 2 === 0) {
            filterErr("the number of arguments({1})is wrong, please check it", length - 1)
        }
        var o = {};
        for (var i = 1; i < length; i = i + 2) {
            o[arguments[i]] = arguments[i + 1]
        }
        return o[value + ""]
    }
}